{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "transfer-learning-cats-dogs.ipynb",
      "provenance": [],
      "collapsed_sections": [],
      "toc_visible": true,
      "include_colab_link": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "accelerator": "GPU"
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/lmoroney/tfbook/blob/master/chapter3/transfer_learning-cats-dogs.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "zX4Kg8DUTKWO",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "ioLbtB3uGKPX",
        "colab": {}
      },
      "source": [
        "try:\n",
        "  # %tensorflow_version only exists in Colab.\n",
        "  %tensorflow_version 2.x\n",
        "except Exception:\n",
        "  pass"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "y23ucAFLoHop",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "import urllib.request\n",
        "import os\n",
        "import zipfile\n",
        "import random\n",
        "from tensorflow.keras.preprocessing.image import ImageDataGenerator\n",
        "from tensorflow.keras import layers\n",
        "from tensorflow.keras import Model\n",
        "from tensorflow.keras.applications.inception_v3 import InceptionV3\n",
        "from tensorflow.keras.optimizers import RMSprop\n",
        "from shutil import copyfile\n",
        "\n",
        "\n",
        "data_url = \"https://download.microsoft.com/download/3/E/1/3E1C3F21-ECDB-4869-8368-6DEBA77B919F/kagglecatsanddogs_3367a.zip\"\n",
        "data_file_name = \"catsdogs.zip\"\n",
        "download_dir = '/tmp/'\n",
        "urllib.request.urlretrieve(data_url, data_file_name)\n",
        "zip_ref = zipfile.ZipFile(data_file_name, 'r')\n",
        "zip_ref.extractall(download_dir)\n",
        "zip_ref.close()\n"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "AwMoZHxWOynx",
        "colab_type": "code",
        "outputId": "8340eb0e-9deb-458d-f9a3-7db550f3ff8d",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 51
        }
      },
      "source": [
        "print(len(os.listdir('/tmp/PetImages/Cat/')))\n",
        "print(len(os.listdir('/tmp/PetImages/Dog/')))\n",
        "\n",
        "# Expected Output:\n",
        "# 12501\n",
        "# 12501"
      ],
      "execution_count": 20,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "12501\n",
            "12501\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "qygIo4W5O1hQ",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "try:\n",
        "    os.mkdir('/tmp/cats-v-dogs')\n",
        "    os.mkdir('/tmp/cats-v-dogs/training')\n",
        "    os.mkdir('/tmp/cats-v-dogs/testing')\n",
        "    os.mkdir('/tmp/cats-v-dogs/training/cats')\n",
        "    os.mkdir('/tmp/cats-v-dogs/training/dogs')\n",
        "    os.mkdir('/tmp/cats-v-dogs/testing/cats')\n",
        "    os.mkdir('/tmp/cats-v-dogs/testing/dogs')\n",
        "except OSError:\n",
        "    pass"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "M90EiIu0O314",
        "colab_type": "code",
        "outputId": "86fbb9c2-8f29-4337-f8ff-faecc984b75c",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 51
        }
      },
      "source": [
        "import random\n",
        "from shutil import copyfile\n",
        "def split_data(SOURCE, TRAINING, TESTING, SPLIT_SIZE):\n",
        "    files = []\n",
        "    for filename in os.listdir(SOURCE):\n",
        "        file = SOURCE + filename\n",
        "        if os.path.getsize(file) > 0:\n",
        "            files.append(filename)\n",
        "        else:\n",
        "            print(filename + \" is zero length, so ignoring.\")\n",
        "\n",
        "    training_length = int(len(files) * SPLIT_SIZE)\n",
        "    testing_length = int(len(files) - training_length)\n",
        "    shuffled_set = random.sample(files, len(files))\n",
        "    training_set = shuffled_set[0:training_length]\n",
        "    testing_set = shuffled_set[:testing_length]\n",
        "\n",
        "    for filename in training_set:\n",
        "        this_file = SOURCE + filename\n",
        "        destination = TRAINING + filename\n",
        "        copyfile(this_file, destination)\n",
        "\n",
        "    for filename in testing_set:\n",
        "        this_file = SOURCE + filename\n",
        "        destination = TESTING + filename\n",
        "        copyfile(this_file, destination)\n",
        "\n",
        "\n",
        "CAT_SOURCE_DIR = \"/tmp/PetImages/Cat/\"\n",
        "TRAINING_CATS_DIR = \"/tmp/cats-v-dogs/training/cats/\"\n",
        "TESTING_CATS_DIR = \"/tmp/cats-v-dogs/testing/cats/\"\n",
        "DOG_SOURCE_DIR = \"/tmp/PetImages/Dog/\"\n",
        "TRAINING_DOGS_DIR = \"/tmp/cats-v-dogs/training/dogs/\"\n",
        "TESTING_DOGS_DIR = \"/tmp/cats-v-dogs/testing/dogs/\"\n",
        "\n",
        "split_size = .9\n",
        "split_data(CAT_SOURCE_DIR, TRAINING_CATS_DIR, TESTING_CATS_DIR, split_size)\n",
        "split_data(DOG_SOURCE_DIR, TRAINING_DOGS_DIR, TESTING_DOGS_DIR, split_size)\n",
        "\n",
        "# Expected output\n",
        "# 666.jpg is zero length, so ignoring\n",
        "# 11702.jpg is zero length, so ignoring"
      ],
      "execution_count": 22,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "666.jpg is zero length, so ignoring.\n",
            "11702.jpg is zero length, so ignoring.\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "cl8sQpM1O9xK",
        "colab_type": "code",
        "outputId": "43b61f0c-c29b-44f6-ecb9-d2e8b4939c4a",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 85
        }
      },
      "source": [
        "\n",
        "print(len(os.listdir('/tmp/cats-v-dogs/training/cats/')))\n",
        "print(len(os.listdir('/tmp/cats-v-dogs/training/dogs/')))\n",
        "print(len(os.listdir('/tmp/cats-v-dogs/testing/cats/')))\n",
        "print(len(os.listdir('/tmp/cats-v-dogs/testing/dogs/')))\n"
      ],
      "execution_count": 23,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "12381\n",
            "12375\n",
            "2361\n",
            "2383\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "TVO1l8vAPE14",
        "colab_type": "code",
        "outputId": "8b4d9565-3b90-4997-b363-83e1f8c79532",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 51
        }
      },
      "source": [
        "\n",
        "TRAINING_DIR = \"/tmp/cats-v-dogs/training/\"\n",
        "# Experiment with your own parameters here to really try to drive it to 99.9% accuracy or better\n",
        "train_datagen = ImageDataGenerator(rescale=1./255,\n",
        "      rotation_range=40,\n",
        "      width_shift_range=0.2,\n",
        "      height_shift_range=0.2,\n",
        "      shear_range=0.2,\n",
        "      zoom_range=0.2,\n",
        "      horizontal_flip=True,\n",
        "      fill_mode='nearest')\n",
        "train_generator = train_datagen.flow_from_directory(TRAINING_DIR,\n",
        "                                                    batch_size=100,\n",
        "                                                    class_mode='binary',\n",
        "                                                    target_size=(150, 150))\n",
        "\n",
        "VALIDATION_DIR = \"/tmp/cats-v-dogs/testing/\"\n",
        "# Experiment with your own parameters here to really try to drive it to 99.9% accuracy or better\n",
        "validation_datagen = ImageDataGenerator(rescale=1./255)\n",
        "validation_generator = validation_datagen.flow_from_directory(VALIDATION_DIR,\n",
        "                                                              batch_size=100,\n",
        "                                                              class_mode='binary',\n",
        "                                                              target_size=(150, 150))\n",
        "\n"
      ],
      "execution_count": 24,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "Found 24754 images belonging to 2 classes.\n",
            "Found 4744 images belonging to 2 classes.\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "tiPK1LlMOvm7",
        "colab_type": "code",
        "outputId": "b3069ce6-838a-40a3-b832-232e15b0ad37",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        }
      },
      "source": [
        "weights_url = \"https://storage.googleapis.com/mledu-datasets/inception_v3_weights_tf_dim_ordering_tf_kernels_notop.h5\"\n",
        "weights_file = \"inception_v3.h5\"\n",
        "urllib.request.urlretrieve(weights_url, weights_file)\n",
        "\n",
        "pre_trained_model = InceptionV3(input_shape=(150, 150, 3),\n",
        "                                include_top=False,\n",
        "                                weights=None)\n",
        "\n",
        "pre_trained_model.load_weights(weights_file)\n",
        "\n",
        "for layer in pre_trained_model.layers:\n",
        "    layer.trainable = False\n",
        "\n",
        "# pre_trained_model.summary()\n",
        "\n",
        "last_layer = pre_trained_model.get_layer('mixed7')\n",
        "print('last layer output shape: ', last_layer.output_shape)\n",
        "last_output = last_layer.output\n",
        "\n"
      ],
      "execution_count": 25,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "last layer output shape:  (None, 7, 7, 768)\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "3nxUncKWPRhR",
        "colab_type": "code",
        "outputId": "d07bd08a-3de8-460d-c311-68d808c0b2f4",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 1000
        }
      },
      "source": [
        "# Flatten the output layer to 1 dimension\n",
        "x = layers.Flatten()(last_output)\n",
        "# Add a fully connected layer with 1,024 hidden units and ReLU activation\n",
        "x = layers.Dense(1024, activation='relu')(x)\n",
        "# Add a dropout rate of 0.2\n",
        "#x = layers.Dropout(0.2)(x)\n",
        "# Add a final sigmoid layer for classification\n",
        "x = layers.Dense(1, activation='sigmoid')(x)\n",
        "\n",
        "model = Model(pre_trained_model.input, x)\n",
        "\n",
        "model.compile(optimizer=RMSprop(lr=0.0001),\n",
        "              loss='binary_crossentropy',\n",
        "              metrics=['acc'])\n",
        "\n",
        "history = model.fit_generator(\n",
        "            train_generator,\n",
        "            validation_data=validation_generator,\n",
        "            epochs=20,\n",
        "            verbose=1)"
      ],
      "execution_count": 26,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "WARNING:tensorflow:sample_weight modes were coerced from\n",
            "  ...\n",
            "    to  \n",
            "  ['...']\n",
            "WARNING:tensorflow:sample_weight modes were coerced from\n",
            "  ...\n",
            "    to  \n",
            "  ['...']\n",
            "Train for 248 steps, validate for 48 steps\n",
            "Epoch 1/20\n",
            "  9/248 [>.............................] - ETA: 3:38 - loss: 0.8552 - acc: 0.6989"
          ],
          "name": "stdout"
        },
        {
          "output_type": "stream",
          "text": [
            "/usr/local/lib/python3.6/dist-packages/PIL/TiffImagePlugin.py:784: UserWarning: Possibly corrupt EXIF data.  Expecting to read 32 bytes but only got 0. Skipping tag 270\n",
            "  \" Skipping tag %s\" % (size, len(data), tag)\n",
            "/usr/local/lib/python3.6/dist-packages/PIL/TiffImagePlugin.py:784: UserWarning: Possibly corrupt EXIF data.  Expecting to read 5 bytes but only got 0. Skipping tag 271\n",
            "  \" Skipping tag %s\" % (size, len(data), tag)\n",
            "/usr/local/lib/python3.6/dist-packages/PIL/TiffImagePlugin.py:784: UserWarning: Possibly corrupt EXIF data.  Expecting to read 8 bytes but only got 0. Skipping tag 272\n",
            "  \" Skipping tag %s\" % (size, len(data), tag)\n",
            "/usr/local/lib/python3.6/dist-packages/PIL/TiffImagePlugin.py:784: UserWarning: Possibly corrupt EXIF data.  Expecting to read 8 bytes but only got 0. Skipping tag 282\n",
            "  \" Skipping tag %s\" % (size, len(data), tag)\n",
            "/usr/local/lib/python3.6/dist-packages/PIL/TiffImagePlugin.py:784: UserWarning: Possibly corrupt EXIF data.  Expecting to read 8 bytes but only got 0. Skipping tag 283\n",
            "  \" Skipping tag %s\" % (size, len(data), tag)\n",
            "/usr/local/lib/python3.6/dist-packages/PIL/TiffImagePlugin.py:784: UserWarning: Possibly corrupt EXIF data.  Expecting to read 20 bytes but only got 0. Skipping tag 306\n",
            "  \" Skipping tag %s\" % (size, len(data), tag)\n",
            "/usr/local/lib/python3.6/dist-packages/PIL/TiffImagePlugin.py:784: UserWarning: Possibly corrupt EXIF data.  Expecting to read 48 bytes but only got 0. Skipping tag 532\n",
            "  \" Skipping tag %s\" % (size, len(data), tag)\n",
            "/usr/local/lib/python3.6/dist-packages/PIL/TiffImagePlugin.py:802: UserWarning: Corrupt EXIF data.  Expecting to read 2 bytes but only got 0. \n",
            "  warnings.warn(str(msg))\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "stream",
          "text": [
            "248/248 [==============================] - 184s 742ms/step - loss: 0.3490 - acc: 0.8440 - val_loss: 0.3113 - val_acc: 0.9429\n",
            "Epoch 2/20\n",
            "248/248 [==============================] - 179s 724ms/step - loss: 0.2629 - acc: 0.8835 - val_loss: 0.4589 - val_acc: 0.9387\n",
            "Epoch 3/20\n",
            "248/248 [==============================] - 180s 726ms/step - loss: 0.2458 - acc: 0.8928 - val_loss: 0.4392 - val_acc: 0.9484\n",
            "Epoch 4/20\n",
            "248/248 [==============================] - 180s 725ms/step - loss: 0.2324 - acc: 0.9048 - val_loss: 0.3632 - val_acc: 0.9576\n",
            "Epoch 5/20\n",
            "248/248 [==============================] - 179s 721ms/step - loss: 0.2232 - acc: 0.9082 - val_loss: 0.4525 - val_acc: 0.9528\n",
            "Epoch 6/20\n",
            "248/248 [==============================] - 179s 721ms/step - loss: 0.2171 - acc: 0.9116 - val_loss: 0.3821 - val_acc: 0.9614\n",
            "Epoch 7/20\n",
            "248/248 [==============================] - 179s 721ms/step - loss: 0.2108 - acc: 0.9144 - val_loss: 0.4416 - val_acc: 0.9602\n",
            "Epoch 8/20\n",
            "248/248 [==============================] - 180s 725ms/step - loss: 0.2075 - acc: 0.9163 - val_loss: 0.8048 - val_acc: 0.9403\n",
            "Epoch 9/20\n",
            "248/248 [==============================] - 178s 717ms/step - loss: 0.2038 - acc: 0.9182 - val_loss: 0.4472 - val_acc: 0.9621\n",
            "Epoch 10/20\n",
            "248/248 [==============================] - 177s 714ms/step - loss: 0.2002 - acc: 0.9197 - val_loss: 0.4092 - val_acc: 0.9667\n",
            "Epoch 11/20\n",
            "248/248 [==============================] - 177s 716ms/step - loss: 0.2001 - acc: 0.9192 - val_loss: 0.3958 - val_acc: 0.9673\n",
            "Epoch 12/20\n",
            "248/248 [==============================] - 176s 711ms/step - loss: 0.1895 - acc: 0.9234 - val_loss: 0.4195 - val_acc: 0.9659\n",
            "Epoch 13/20\n",
            "248/248 [==============================] - 177s 716ms/step - loss: 0.1870 - acc: 0.9266 - val_loss: 0.4096 - val_acc: 0.9671\n",
            "Epoch 14/20\n",
            "248/248 [==============================] - 177s 715ms/step - loss: 0.1873 - acc: 0.9261 - val_loss: 0.4035 - val_acc: 0.9686\n",
            "Epoch 15/20\n",
            "248/248 [==============================] - 178s 718ms/step - loss: 0.1853 - acc: 0.9275 - val_loss: 0.4662 - val_acc: 0.9623\n",
            "Epoch 16/20\n",
            "248/248 [==============================] - 177s 713ms/step - loss: 0.1811 - acc: 0.9308 - val_loss: 0.4708 - val_acc: 0.9631\n",
            "Epoch 17/20\n",
            "248/248 [==============================] - 177s 715ms/step - loss: 0.1793 - acc: 0.9303 - val_loss: 0.4561 - val_acc: 0.9640\n",
            "Epoch 18/20\n",
            "248/248 [==============================] - 179s 722ms/step - loss: 0.1780 - acc: 0.9284 - val_loss: 0.6377 - val_acc: 0.9566\n",
            "Epoch 19/20\n",
            "248/248 [==============================] - 177s 715ms/step - loss: 0.1738 - acc: 0.9301 - val_loss: 0.5607 - val_acc: 0.9604\n",
            "Epoch 20/20\n",
            "248/248 [==============================] - 176s 711ms/step - loss: 0.1759 - acc: 0.9307 - val_loss: 0.5651 - val_acc: 0.9602\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "erDopoQ5eNL7",
        "colab_type": "code",
        "outputId": "2d3d39a7-b77c-435d-bd57-d28af9259ec5",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 315
        }
      },
      "source": [
        "%matplotlib inline\n",
        "\n",
        "import matplotlib.image  as mpimg\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "#-----------------------------------------------------------\n",
        "# Retrieve a list of list results on training and test data\n",
        "# sets for each training epoch\n",
        "#-----------------------------------------------------------\n",
        "acc=history.history['acc']\n",
        "val_acc=history.history['val_acc']\n",
        "loss=history.history['loss']\n",
        "val_loss=history.history['val_loss']\n",
        "\n",
        "epochs=range(len(acc)) # Get number of epochs\n",
        "\n",
        "#------------------------------------------------\n",
        "# Plot training and validation accuracy per epoch\n",
        "#------------------------------------------------\n",
        "plt.plot(epochs, acc, 'r', \"Training Accuracy\")\n",
        "plt.plot(epochs, val_acc, 'b', \"Validation Accuracy\")\n",
        "plt.title('Training and validation accuracy')\n",
        "plt.figure()\n",
        "\n"
      ],
      "execution_count": 27,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<Figure size 432x288 with 0 Axes>"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 27
        },
        {
          "output_type": "display_data",
          "data": {
            "image/png": "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\nLeA5/ZKiAX0AAAAASUVORK5CYII=\n",
            "text/plain": [
              "<Figure size 432x288 with 1 Axes>"
            ]
          },
          "metadata": {
            "tags": []
          }
        },
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 432x288 with 0 Axes>"
            ]
          },
          "metadata": {
            "tags": []
          }
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "_0R9fsf4w29e",
        "colab_type": "code",
        "colab": {
          "resources": {
            "http://localhost:8080/nbextensions/google.colab/files.js": {
              "data": "Ly8gQ29weXJpZ2h0IDIwMTcgR29vZ2xlIExMQwovLwovLyBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKLy8geW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLgovLyBZb3UgbWF5IG9idGFpbiBhIGNvcHkgb2YgdGhlIExpY2Vuc2UgYXQKLy8KLy8gICAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjAKLy8KLy8gVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQovLyBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiAiQVMgSVMiIEJBU0lTLAovLyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KLy8gU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAovLyBsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS4KCi8qKgogKiBAZmlsZW92ZXJ2aWV3IEhlbHBlcnMgZm9yIGdvb2dsZS5jb2xhYiBQeXRob24gbW9kdWxlLgogKi8KKGZ1bmN0aW9uKHNjb3BlKSB7CmZ1bmN0aW9uIHNwYW4odGV4dCwgc3R5bGVBdHRyaWJ1dGVzID0ge30pIHsKICBjb25zdCBlbGVtZW50ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc3BhbicpOwogIGVsZW1lbnQudGV4dENvbnRlbnQgPSB0ZXh0OwogIGZvciAoY29uc3Qga2V5IG9mIE9iamVjdC5rZXlzKHN0eWxlQXR0cmlidXRlcykpIHsKICAgIGVsZW1lbnQuc3R5bGVba2V5XSA9IHN0eWxlQXR0cmlidXRlc1trZXldOwogIH0KICByZXR1cm4gZWxlbWVudDsKfQoKLy8gTWF4IG51bWJlciBvZiBieXRlcyB3aGljaCB3aWxsIGJlIHVwbG9hZGVkIGF0IGEgdGltZS4KY29uc3QgTUFYX1BBWUxPQURfU0laRSA9IDEwMCAqIDEwMjQ7Ci8vIE1heCBhbW91bnQgb2YgdGltZSB0byBibG9jayB3YWl0aW5nIGZvciB0aGUgdXNlci4KY29uc3QgRklMRV9DSEFOR0VfVElNRU9VVF9NUyA9IDMwICogMTAwMDsKCmZ1bmN0aW9uIF91cGxvYWRGaWxlcyhpbnB1dElkLCBvdXRwdXRJZCkgewogIGNvbnN0IHN0ZXBzID0gdXBsb2FkRmlsZXNTdGVwKGlucHV0SWQsIG91dHB1dElkKTsKICBjb25zdCBvdXRwdXRFbGVtZW50ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQob3V0cHV0SWQpOwogIC8vIENhY2hlIHN0ZXBzIG9uIHRoZSBvdXRwdXRFbGVtZW50IHRvIG1ha2UgaXQgYXZhaWxhYmxlIGZvciB0aGUgbmV4dCBjYWxsCiAgLy8gdG8gdXBsb2FkRmlsZXNDb250aW51ZSBmcm9tIFB5dGhvbi4KICBvdXRwdXRFbGVtZW50LnN0ZXBzID0gc3RlcHM7CgogIHJldHVybiBfdXBsb2FkRmlsZXNDb250aW51ZShvdXRwdXRJZCk7Cn0KCi8vIFRoaXMgaXMgcm91Z2hseSBhbiBhc3luYyBnZW5lcmF0b3IgKG5vdCBzdXBwb3J0ZWQgaW4gdGhlIGJyb3dzZXIgeWV0KSwKLy8gd2hlcmUgdGhlcmUgYXJlIG11bHRpcGxlIGFzeW5jaHJvbm91cyBzdGVwcyBhbmQgdGhlIFB5dGhvbiBzaWRlIGlzIGdvaW5nCi8vIHRvIHBvbGwgZm9yIGNvbXBsZXRpb24gb2YgZWFjaCBzdGVwLgovLyBUaGlzIHVzZXMgYSBQcm9taXNlIHRvIGJsb2NrIHRoZSBweXRob24gc2lkZSBvbiBjb21wbGV0aW9uIG9mIGVhY2ggc3RlcCwKLy8gdGhlbiBwYXNzZXMgdGhlIHJlc3VsdCBvZiB0aGUgcHJldmlvdXMgc3RlcCBhcyB0aGUgaW5wdXQgdG8gdGhlIG5leHQgc3RlcC4KZnVuY3Rpb24gX3VwbG9hZEZpbGVzQ29udGludWUob3V0cHV0SWQpIHsKICBjb25zdCBvdXRwdXRFbGVtZW50ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQob3V0cHV0SWQpOwogIGNvbnN0IHN0ZXBzID0gb3V0cHV0RWxlbWVudC5zdGVwczsKCiAgY29uc3QgbmV4dCA9IHN0ZXBzLm5leHQob3V0cHV0RWxlbWVudC5sYXN0UHJvbWlzZVZhbHVlKTsKICByZXR1cm4gUHJvbWlzZS5yZXNvbHZlKG5leHQudmFsdWUucHJvbWlzZSkudGhlbigodmFsdWUpID0+IHsKICAgIC8vIENhY2hlIHRoZSBsYXN0IHByb21pc2UgdmFsdWUgdG8gbWFrZSBpdCBhdmFpbGFibGUgdG8gdGhlIG5leHQKICAgIC8vIHN0ZXAgb2YgdGhlIGdlbmVyYXRvci4KICAgIG91dHB1dEVsZW1lbnQubGFzdFByb21pc2VWYWx1ZSA9IHZhbHVlOwogICAgcmV0dXJuIG5leHQudmFsdWUucmVzcG9uc2U7CiAgfSk7Cn0KCi8qKgogKiBHZW5lcmF0b3IgZnVuY3Rpb24gd2hpY2ggaXMgY2FsbGVkIGJldHdlZW4gZWFjaCBhc3luYyBzdGVwIG9mIHRoZSB1cGxvYWQKICogcHJvY2Vzcy4KICogQHBhcmFtIHtzdHJpbmd9IGlucHV0SWQgRWxlbWVudCBJRCBvZiB0aGUgaW5wdXQgZmlsZSBwaWNrZXIgZWxlbWVudC4KICogQHBhcmFtIHtzdHJpbmd9IG91dHB1dElkIEVsZW1lbnQgSUQgb2YgdGhlIG91dHB1dCBkaXNwbGF5LgogKiBAcmV0dXJuIHshSXRlcmFibGU8IU9iamVjdD59IEl0ZXJhYmxlIG9mIG5leHQgc3RlcHMuCiAqLwpmdW5jdGlvbiogdXBsb2FkRmlsZXNTdGVwKGlucHV0SWQsIG91dHB1dElkKSB7CiAgY29uc3QgaW5wdXRFbGVtZW50ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoaW5wdXRJZCk7CiAgaW5wdXRFbGVtZW50LmRpc2FibGVkID0gZmFsc2U7CgogIGNvbnN0IG91dHB1dEVsZW1lbnQgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChvdXRwdXRJZCk7CiAgb3V0cHV0RWxlbWVudC5pbm5lckhUTUwgPSAnJzsKCiAgY29uc3QgcGlja2VkUHJvbWlzZSA9IG5ldyBQcm9taXNlKChyZXNvbHZlKSA9PiB7CiAgICBpbnB1dEVsZW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignY2hhbmdlJywgKGUpID0+IHsKICAgICAgcmVzb2x2ZShlLnRhcmdldC5maWxlcyk7CiAgICB9KTsKICB9KTsKCiAgY29uc3QgY2FuY2VsID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnYnV0dG9uJyk7CiAgaW5wdXRFbGVtZW50LnBhcmVudEVsZW1lbnQuYXBwZW5kQ2hpbGQoY2FuY2VsKTsKICBjYW5jZWwudGV4dENvbnRlbnQgPSAnQ2FuY2VsIHVwbG9hZCc7CiAgY29uc3QgY2FuY2VsUHJvbWlzZSA9IG5ldyBQcm9taXNlKChyZXNvbHZlKSA9PiB7CiAgICBjYW5jZWwub25jbGljayA9ICgpID0+IHsKICAgICAgcmVzb2x2ZShudWxsKTsKICAgIH07CiAgfSk7CgogIC8vIENhbmNlbCB1cGxvYWQgaWYgdXNlciBoYXNuJ3QgcGlja2VkIGFueXRoaW5nIGluIHRpbWVvdXQuCiAgY29uc3QgdGltZW91dFByb21pc2UgPSBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4gewogICAgc2V0VGltZW91dCgoKSA9PiB7CiAgICAgIHJlc29sdmUobnVsbCk7CiAgICB9LCBGSUxFX0NIQU5HRV9USU1FT1VUX01TKTsKICB9KTsKCiAgLy8gV2FpdCBmb3IgdGhlIHVzZXIgdG8gcGljayB0aGUgZmlsZXMuCiAgY29uc3QgZmlsZXMgPSB5aWVsZCB7CiAgICBwcm9taXNlOiBQcm9taXNlLnJhY2UoW3BpY2tlZFByb21pc2UsIHRpbWVvdXRQcm9taXNlLCBjYW5jZWxQcm9taXNlXSksCiAgICByZXNwb25zZTogewogICAgICBhY3Rpb246ICdzdGFydGluZycsCiAgICB9CiAgfTsKCiAgaWYgKCFmaWxlcykgewogICAgcmV0dXJuIHsKICAgICAgcmVzcG9uc2U6IHsKICAgICAgICBhY3Rpb246ICdjb21wbGV0ZScsCiAgICAgIH0KICAgIH07CiAgfQoKICBjYW5jZWwucmVtb3ZlKCk7CgogIC8vIERpc2FibGUgdGhlIGlucHV0IGVsZW1lbnQgc2luY2UgZnVydGhlciBwaWNrcyBhcmUgbm90IGFsbG93ZWQuCiAgaW5wdXRFbGVtZW50LmRpc2FibGVkID0gdHJ1ZTsKCiAgZm9yIChjb25zdCBmaWxlIG9mIGZpbGVzKSB7CiAgICBjb25zdCBsaSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2xpJyk7CiAgICBsaS5hcHBlbmQoc3BhbihmaWxlLm5hbWUsIHtmb250V2VpZ2h0OiAnYm9sZCd9KSk7CiAgICBsaS5hcHBlbmQoc3BhbigKICAgICAgICBgKCR7ZmlsZS50eXBlIHx8ICduL2EnfSkgLSAke2ZpbGUuc2l6ZX0gYnl0ZXMsIGAgKwogICAgICAgIGBsYXN0IG1vZGlmaWVkOiAkewogICAgICAgICAgICBmaWxlLmxhc3RNb2RpZmllZERhdGUgPyBmaWxlLmxhc3RNb2RpZmllZERhdGUudG9Mb2NhbGVEYXRlU3RyaW5nKCkgOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnbi9hJ30gLSBgKSk7CiAgICBjb25zdCBwZXJjZW50ID0gc3BhbignMCUgZG9uZScpOwogICAgbGkuYXBwZW5kQ2hpbGQocGVyY2VudCk7CgogICAgb3V0cHV0RWxlbWVudC5hcHBlbmRDaGlsZChsaSk7CgogICAgY29uc3QgZmlsZURhdGFQcm9taXNlID0gbmV3IFByb21pc2UoKHJlc29sdmUpID0+IHsKICAgICAgY29uc3QgcmVhZGVyID0gbmV3IEZpbGVSZWFkZXIoKTsKICAgICAgcmVhZGVyLm9ubG9hZCA9IChlKSA9PiB7CiAgICAgICAgcmVzb2x2ZShlLnRhcmdldC5yZXN1bHQpOwogICAgICB9OwogICAgICByZWFkZXIucmVhZEFzQXJyYXlCdWZmZXIoZmlsZSk7CiAgICB9KTsKICAgIC8vIFdhaXQgZm9yIHRoZSBkYXRhIHRvIGJlIHJlYWR5LgogICAgbGV0IGZpbGVEYXRhID0geWllbGQgewogICAgICBwcm9taXNlOiBmaWxlRGF0YVByb21pc2UsCiAgICAgIHJlc3BvbnNlOiB7CiAgICAgICAgYWN0aW9uOiAnY29udGludWUnLAogICAgICB9CiAgICB9OwoKICAgIC8vIFVzZSBhIGNodW5rZWQgc2VuZGluZyB0byBhdm9pZCBtZXNzYWdlIHNpemUgbGltaXRzLiBTZWUgYi82MjExNTY2MC4KICAgIGxldCBwb3NpdGlvbiA9IDA7CiAgICB3aGlsZSAocG9zaXRpb24gPCBmaWxlRGF0YS5ieXRlTGVuZ3RoKSB7CiAgICAgIGNvbnN0IGxlbmd0aCA9IE1hdGgubWluKGZpbGVEYXRhLmJ5dGVMZW5ndGggLSBwb3NpdGlvbiwgTUFYX1BBWUxPQURfU0laRSk7CiAgICAgIGNvbnN0IGNodW5rID0gbmV3IFVpbnQ4QXJyYXkoZmlsZURhdGEsIHBvc2l0aW9uLCBsZW5ndGgpOwogICAgICBwb3NpdGlvbiArPSBsZW5ndGg7CgogICAgICBjb25zdCBiYXNlNjQgPSBidG9hKFN0cmluZy5mcm9tQ2hhckNvZGUuYXBwbHkobnVsbCwgY2h1bmspKTsKICAgICAgeWllbGQgewogICAgICAgIHJlc3BvbnNlOiB7CiAgICAgICAgICBhY3Rpb246ICdhcHBlbmQnLAogICAgICAgICAgZmlsZTogZmlsZS5uYW1lLAogICAgICAgICAgZGF0YTogYmFzZTY0LAogICAgICAgIH0sCiAgICAgIH07CiAgICAgIHBlcmNlbnQudGV4dENvbnRlbnQgPQogICAgICAgICAgYCR7TWF0aC5yb3VuZCgocG9zaXRpb24gLyBmaWxlRGF0YS5ieXRlTGVuZ3RoKSAqIDEwMCl9JSBkb25lYDsKICAgIH0KICB9CgogIC8vIEFsbCBkb25lLgogIHlpZWxkIHsKICAgIHJlc3BvbnNlOiB7CiAgICAgIGFjdGlvbjogJ2NvbXBsZXRlJywKICAgIH0KICB9Owp9CgpzY29wZS5nb29nbGUgPSBzY29wZS5nb29nbGUgfHwge307CnNjb3BlLmdvb2dsZS5jb2xhYiA9IHNjb3BlLmdvb2dsZS5jb2xhYiB8fCB7fTsKc2NvcGUuZ29vZ2xlLmNvbGFiLl9maWxlcyA9IHsKICBfdXBsb2FkRmlsZXMsCiAgX3VwbG9hZEZpbGVzQ29udGludWUsCn07Cn0pKHNlbGYpOwo=",
              "ok": true,
              "headers": [
                [
                  "content-type",
                  "application/javascript"
                ]
              ],
              "status": 200,
              "status_text": ""
            }
          },
          "base_uri": "https://localhost:8080/",
          "height": 125
        },
        "outputId": "a9ab5fdf-a939-4991-ef9d-b92d28964519"
      },
      "source": [
        "import numpy as np\n",
        "from google.colab import files\n",
        "from keras.preprocessing import image\n",
        "\n",
        "uploaded = files.upload()\n",
        "\n",
        "for fn in uploaded.keys():\n",
        " \n",
        "  # predicting images\n",
        "  path = '/content/' + fn\n",
        "  img = image.load_img(path, target_size=(150, 150))\n",
        "  x = image.img_to_array(img)\n",
        "  x = np.expand_dims(x, axis=0)\n",
        "\n",
        "  image_tensor = np.vstack([x])\n",
        "  classes = model.predict(image_tensor)\n",
        "  print(classes)\n",
        "  print(classes[0])\n",
        "  if classes[0]>0.5:\n",
        "    print(fn + \" is a dog\")\n",
        "  else:\n",
        "    print(fn + \" is a cat\")"
      ],
      "execution_count": 38,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "text/html": [
              "\n",
              "     <input type=\"file\" id=\"files-751fd926-da30-4ac0-ae58-07db8f805e9d\" name=\"files[]\" multiple disabled />\n",
              "     <output id=\"result-751fd926-da30-4ac0-ae58-07db8f805e9d\">\n",
              "      Upload widget is only available when the cell has been executed in the\n",
              "      current browser session. Please rerun this cell to enable.\n",
              "      </output>\n",
              "      <script src=\"/nbextensions/google.colab/files.js\"></script> "
            ],
            "text/plain": [
              "<IPython.core.display.HTML object>"
            ]
          },
          "metadata": {
            "tags": []
          }
        },
        {
          "output_type": "stream",
          "text": [
            "Saving cat-984097_640.jpg to cat-984097_640.jpg\n",
            "[[0.]]\n",
            "[0.]\n",
            "cat-984097_640.jpg is a cat\n"
          ],
          "name": "stdout"
        }
      ]
    }
  ]
}
